﻿using System;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Xml;
using System.Xml.Linq;
using System.Globalization;
using System.Reflection;
using System.Data.Metadata.Edm;
using System.Data.Mapping;
using System.Data.Entity.Design;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Text;


// Copyright (c) Microsoft Corporation.  All rights reserved.

/// <summary>
/// Responsible for helping to create source code that is
/// correctly formated and functional
/// </summary>
public class CodeGenerationTools
{
    private readonly DynamicTextTransformation _textTransformation;
    private readonly CSharpCodeProvider _code;
    private readonly MetadataTools _ef;

    /// <summary>
    /// Initializes a new CodeGenerationTools object with the TextTransformation (T4 generated class)
    /// that is currently running
    /// </summary>
    public CodeGenerationTools(object textTransformation)
    {
        if (textTransformation == null)
        {
            throw new ArgumentNullException("textTransformation");
        }

        _textTransformation = DynamicTextTransformation.Create(textTransformation);
        _code = new CSharpCodeProvider();
        _ef = new MetadataTools(_textTransformation);
        FullyQualifySystemTypes = false;
        CamelCaseFields = true;
    }

    /// <summary>
    /// When true, all types that are not being generated
    /// are fully qualified to keep them from conflicting with
    /// types that are being generated. Useful when you have
    /// something like a type being generated named System.
    ///
    /// Default is false.
    /// </summary>
    public bool FullyQualifySystemTypes { get; set; }

    /// <summary>
    /// When true, the field names are Camel Cased,
    /// otherwise they will preserve the case they
    /// start with.
    ///
    /// Default is true.
    /// </summary>
    public bool CamelCaseFields { get; set; }

    /// <summary>
    /// Returns the NamespaceName suggested by VS if running inside VS.  Otherwise, returns
    /// null.
    /// </summary>
    public string VsNamespaceSuggestion()
    {
        string suggestion = _textTransformation.Host.ResolveParameterValue("directiveId", "namespaceDirectiveProcessor", "namespaceHint");
        if (String.IsNullOrEmpty(suggestion))
        {
            return null;
        }

        return suggestion;
    }

    /// <summary>
    /// Returns a string that is safe for use as an identifier in C#.
    /// Keywords are escaped.
    /// </summary>
    public string Escape(string name)
    {
        if (name == null)
        {
            return null;
        }

        return _code.CreateEscapedIdentifier(name);
    }

    /// <summary>
    /// Returns the name of the TypeUsage's EdmType that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(TypeUsage typeUsage)
    {
        if (typeUsage == null)
        {
            return null;
        }

        if (typeUsage.EdmType is ComplexType ||
            typeUsage.EdmType is EntityType)
        {
            return Escape(typeUsage.EdmType.Name);
        }
        else if (typeUsage.EdmType is PrimitiveType)
        {

            Type clrType = _ef.ClrType(typeUsage);
            string typeName = Escape(clrType);
            if (clrType.IsValueType && _ef.IsNullable(typeUsage))
            {
                return String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName);
            }

            return typeName;
        }
        else if (typeUsage.EdmType is CollectionType)
        {
            return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", Escape(((CollectionType)typeUsage.EdmType).TypeUsage));
        }


        throw new ArgumentException("typeUsage");
    }

    /// <summary>
    /// Returns the name of the EdmMember that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EdmMember member)
    {
        if (member == null)
        {
            return null;
        }

        return Escape(member.Name);
    }

    /// <summary>
    /// Returns the name of the EdmType that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EdmType type)
    {
        if (type == null)
        {
            return null;
        }

        return Escape(type.Name);
    }

    /// <summary>
    /// Returns the name of the EdmFunction that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EdmFunction function)
    {
        if (function == null)
        {
            return null;
        }

        return Escape(function.Name);
    }

    /// <summary>
    /// Returns the name of the EntityContainer that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EntityContainer container)
    {
        if (container == null)
        {
            return null;
        }

        return Escape(container.Name);
    }

    /// <summary>
    /// Returns the name of the EntitySet that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(EntitySet set)
    {
        if (set == null)
        {
            return null;
        }

        return Escape(set.Name);
    }

    /// <summary>
    /// Returns the name of the StructuralType that is safe for
    /// use as an identifier.
    /// </summary>
    public string Escape(StructuralType type)
    {
        if (type == null)
        {
            return null;
        }

        return Escape(type.Name);
    }

    /// <summary>
    /// Returns the NamespaceName with each segment safe to
    /// use as an identifier.
    /// </summary>
    public string EscapeNamespace(string namespaceName)
    {
        if (String.IsNullOrEmpty(namespaceName))
        {
            return namespaceName;
        }

        string[] parts = namespaceName.Split('.');
        namespaceName = String.Empty;
        foreach (string part in parts)
        {
            if (namespaceName != String.Empty)
            {
                namespaceName += ".";
            }

            namespaceName += Escape(part);
        }

        return namespaceName;
    }

    /// <summary>
    /// Returns the name of the EdmMember formatted for
    /// use as a field identifier.
    ///
    /// This method changes behavior based on the CamelCaseFields
    /// setting.
    /// </summary>
    public string FieldName(EdmMember member)
    {
        if (member == null)
        {
            return null;
        }

        return FieldName(member.Name);
    }

    /// <summary>
    /// Returns the name of the EntitySet formatted for
    /// use as a field identifier.
    ///
    /// This method changes behavior based on the CamelCaseFields
    /// setting.
    /// </summary>
    public string FieldName(EntitySet set)
    {
        if (set == null)
        {
            return null;
        }

        return FieldName(set.Name);

    }

    private string FieldName(string name)
    {
        if (CamelCaseFields)
        {
            return "_" + CamelCase(name);
        }
        else
        {
            return "_" + name;
        }
    }

    /// <summary>
    /// Returns the name of the Type object formatted for
    /// use in source code.
    ///
    /// This method changes behavior based on the FullyQualifySystemTypes
    /// setting.
    /// </summary>
    public string Escape(Type clrType)
    {
        if (clrType == null)
        {
            return null;
        }

        string typeName;
        if (FullyQualifySystemTypes)
        {
            typeName = "global::" + clrType.FullName;
        }
        else
        {
            typeName = _code.GetTypeOutput(new CodeTypeReference(clrType));
        }
        return typeName;
    }



    /// <summary>
    /// Returns the abstract option if the entity is Abstract, otherwise returns String.Empty
    /// </summary>
    public string AbstractOption(EntityType entity)
    {
        if (entity.Abstract)
        {
            return "abstract";
        }
        return String.Empty;
    }

    /// <summary>
    /// Returns the passed in identifier with the first letter changed to lowercase
    /// </summary>
    public string CamelCase(string identifier)
    {
        if (String.IsNullOrEmpty(identifier))
        {
            return identifier;
        }

        if (identifier.Length == 1)
        {
            return identifier[0].ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
        }

        return identifier[0].ToString(CultureInfo.InvariantCulture).ToLowerInvariant() + identifier.Substring(1);
    }

    /// <summary>
    /// If the value parameter is null or empty an empty string is returned,
    /// otherwise it retuns value with a single space concatenated on the end.
    /// </summary>
    public string SpaceAfter(string value)
    {
        return StringAfter(value, " ");
    }

    /// <summary>
    /// If the value parameter is null or empty an empty string is returned,
    /// otherwise it retuns value with a single space concatenated on the end.
    /// </summary>
    public string SpaceBefore(string value)
    {
        return StringBefore(" ", value);
    }

    /// <summary>
    /// If the value parameter is null or empty an empty string is returned,
    /// otherwise it retuns value with append concatenated on the end.
    /// </summary>
    public string StringAfter(string value, string append)
    {
        if (String.IsNullOrEmpty(value))
        {
            return String.Empty;
        }

        return value + append;
    }

    /// <summary>
    /// If the value parameter is null or empty an empty string is returned,
    /// otherwise it retuns value with prepend concatenated on the front.
    /// </summary>
    public string StringBefore(string prepend, string value)
    {
        if (String.IsNullOrEmpty(value))
        {
            return String.Empty;
        }

        return prepend + value;
    }

    /// <summary>
    /// Retuns as full of a name as possible, if a namespace is provided
    /// the namespace and name are combined with a period, otherwise just
    /// the name is returned.
    /// </summary>
    public string CreateFullName(string namespaceName, string name)
    {
        if (String.IsNullOrEmpty(namespaceName))
        {
            return name;
        }

        return namespaceName + "." + name;
    }

    public string CreateLiteral(object value)
    {
        if (value == null)
        {
            return string.Empty;
        }

        Type type = value.GetType();
        if (type.IsEnum)
        {
            return type.FullName + "." + value.ToString();
        }
        if (type == typeof(Guid))
        {
            return string.Format(CultureInfo.InvariantCulture, "new Guid(\"{0}\")",
                                 ((Guid)value).ToString("D", CultureInfo.InvariantCulture));
        }
        else if (type == typeof(DateTime))
        {
            return string.Format(CultureInfo.InvariantCulture, "new DateTime({0}, DateTimeKind.Unspecified)",
                                 ((DateTime)value).Ticks);
        }
        else if (type == typeof(byte[]))
        {
            var arrayInit = string.Join(", ", ((byte[])value).Select(b => b.ToString(CultureInfo.InvariantCulture)).ToArray());
            return string.Format(CultureInfo.InvariantCulture, "new Byte[] {{{0}}}", arrayInit);
        }
        else if (type == typeof(DateTimeOffset))
        {
            var dto = (DateTimeOffset)value;
            return string.Format(CultureInfo.InvariantCulture, "new DateTimeOffset({0}, new TimeSpan({1}))",
                                 dto.Ticks, dto.Offset.Ticks);
        }

        var expression = new CodePrimitiveExpression(value);
        var writer = new StringWriter();
        CSharpCodeProvider code = new CSharpCodeProvider();
        code.GenerateCodeFromExpression(expression, writer, new CodeGeneratorOptions());
        return writer.ToString();
    }
}

/// <summary>
/// Responsible for making the Entity Framework Metadata more
/// accessible for code generation.
/// </summary>
public class MetadataTools
{
    private readonly DynamicTextTransformation _textTransformation;

    /// <summary>
    /// Initializes an MetadataTools Instance  with the
    /// TextTransformation (T4 generated class) that is currently running
    /// </summary>
    public MetadataTools(object textTransformation)
    {
        if (textTransformation == null)
        {
            throw new ArgumentNullException("textTransformation");
        }

        _textTransformation = DynamicTextTransformation.Create(textTransformation);
    }

    /// <summary>
    /// If the passed in TypeUsage has an EdmType that is a PrimitiveType, this method returns
    /// the corosponding Type object, otherwise it returns the Type object for Object.
    /// </summary>
    public Type ClrType(TypeUsage typeUsage)
    {
        if (typeUsage.EdmType is PrimitiveType)
        {
            return ((PrimitiveType)typeUsage.EdmType).ClrEquivalentType;
        }

        return typeof(object);
    }

    /// <summary>
    /// True if the EdmProperty is a key of its DeclaringType, False otherwise.
    /// </summary>
    public bool IsKey(EdmProperty property)
    {
        if (property != null && property.DeclaringType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
        {
            return ((EntityType)property.DeclaringType).KeyMembers.Contains(property);
        }

        return false;
    }

    /// <summary>
    /// True if the EdmProperty TypeUsage is Nullable, False otherwise.
    /// </summary>
    public bool IsNullable(EdmProperty property)
    {
        return property != null && IsNullable(property.TypeUsage);
    }

    /// <summary>
    /// True if the TypeUsage is Nullable, False otherwise.
    /// </summary>
    public bool IsNullable(TypeUsage typeUsage)
    {
        Facet nullableFacet = null;
        if (typeUsage != null &&
            typeUsage.Facets.TryGetValue("Nullable", true, out nullableFacet))
        {
            return (bool)nullableFacet.Value;
        }

        return false;
    }

    /// <summary>
    /// If the passed in TypeUsage represents a collection this method returns final element
    /// type of the collection, otherwise it returns the value passed in.
    /// </summary>
    public TypeUsage GetElementType(TypeUsage typeUsage)
    {
        if (typeUsage == null)
        {
            return null;
        }

        if (typeUsage.EdmType is CollectionType)
        {
            return GetElementType(((CollectionType)typeUsage.EdmType).TypeUsage);
        }
        else
        {
            return typeUsage;
        }
    }

    /// <summary>
    /// Returns the NavigationProperty that is the other end of the same association set if it is
    /// available, otherwise it returns null.
    /// </summary>
    public NavigationProperty Inverse(NavigationProperty navProperty)
    {
        if (navProperty == null)
        {
            return null;
        }

        EntityType toEntity = navProperty.ToEndMember.GetEntityType();
        return toEntity.NavigationProperties
            .SingleOrDefault(n => Object.ReferenceEquals(n.RelationshipType, navProperty.RelationshipType) && !Object.ReferenceEquals(n, navProperty));
    }

    /// <summary>
    /// Given a property on the dependent end of a referential constraint, returns the corresponding property on the principal end.
    /// Requires: The association has a referential constraint, and the specified dependentProperty is one of the properties on the dependent end.
    /// </summary>
    public EdmProperty GetCorrespondingPrincipalProperty(NavigationProperty navProperty, EdmProperty dependentProperty)
    {
        if (navProperty == null)
        {
            throw new ArgumentNullException("navProperty");
        }

        if (dependentProperty == null)
        {
            throw new ArgumentNullException("dependentProperty");
        }

        ReadOnlyMetadataCollection<EdmProperty> fromProperties = GetPrincipalProperties(navProperty);
        ReadOnlyMetadataCollection<EdmProperty> toProperties = GetDependentProperties(navProperty);

        if (toProperties.IndexOf(dependentProperty) == -1)
        {
            return null;
        }

        return fromProperties[toProperties.IndexOf(dependentProperty)];
    }


    /// <summary>
    /// Given a property on the principal end of a referential constraint, returns the corresponding property on the dependent end.
    /// Requires: The association has a referential constraint, and the specified principalProperty is one of the properties on the principal end.
    /// </summary>
    public EdmProperty GetCorrespondingDependentProperty(NavigationProperty navProperty, EdmProperty principalProperty)
    {
        if (navProperty == null)
        {
            throw new ArgumentNullException("navProperty");
        }

        if (principalProperty == null)
        {
            throw new ArgumentNullException("principalProperty");
        }

        ReadOnlyMetadataCollection<EdmProperty> fromProperties = GetPrincipalProperties(navProperty);
        ReadOnlyMetadataCollection<EdmProperty> toProperties = GetDependentProperties(navProperty);
        return toProperties[fromProperties.IndexOf(principalProperty)];
    }

    /// <summary>
    /// Gets the collection of properties that are on the principal end of a referential constraint for the specified navigation property.
    /// Requires: The association has a referential constraint.
    /// </summary>
    public ReadOnlyMetadataCollection<EdmProperty> GetPrincipalProperties(NavigationProperty navProperty)
    {
        if (navProperty == null)
        {
            throw new ArgumentNullException("navProperty");
        }

        return ((AssociationType)navProperty.RelationshipType).ReferentialConstraints[0].FromProperties;
    }

    /// <summary>
    /// Gets the collection of properties that are on the dependent end of a referential constraint for the specified navigation property.
    /// Requires: The association has a referential constraint.
    /// </summary>
    public ReadOnlyMetadataCollection<EdmProperty> GetDependentProperties(NavigationProperty navProperty)
    {
        if (navProperty == null)
        {
            throw new ArgumentNullException("navProperty");
        }

        return ((AssociationType)navProperty.RelationshipType).ReferentialConstraints[0].ToProperties;
    }

    /// <summary>
    /// True if this entity type requires the HandleCascadeDelete method defined and the method has
    /// not been defined on any base type
    /// </summary>
    public bool NeedsHandleCascadeDeleteMethod(ItemCollection itemCollection, EntityType entity)
    {
        bool needsMethod = ContainsCascadeDeleteAssociation(itemCollection, entity);
        // Check to make sure no base types have already declared this method
        EntityType baseType = entity.BaseType as EntityType;
        while (needsMethod && baseType != null)
        {
            needsMethod = !ContainsCascadeDeleteAssociation(itemCollection, baseType);
            baseType = baseType.BaseType as EntityType;
        }
        return needsMethod;
    }

    /// <summary>
    /// True if this entity type participates in any relationships where the other end has an OnDelete
    /// cascade delete defined, or if it is the dependent in any identifying relationships
    /// </summary>
    private bool ContainsCascadeDeleteAssociation(ItemCollection itemCollection, EntityType entity)
    {
        return itemCollection.GetItems<AssociationType>().Where(a =>
                ((RefType)a.AssociationEndMembers[0].TypeUsage.EdmType).ElementType == entity && IsCascadeDeletePrincipal(a.AssociationEndMembers[1]) ||
                ((RefType)a.AssociationEndMembers[1].TypeUsage.EdmType).ElementType == entity && IsCascadeDeletePrincipal(a.AssociationEndMembers[0])).Any();
    }

    /// <summary>
    /// True if the source end of the specified navigation property is the principal in an identifying relationship.
    /// or if the source end has cascade delete defined.
    /// </summary>
    public bool IsCascadeDeletePrincipal(NavigationProperty navProperty)
    {
        if (navProperty == null)
        {
            throw new ArgumentNullException("navProperty");
        }

        return IsCascadeDeletePrincipal((AssociationEndMember)navProperty.FromEndMember);
    }

    /// <summary>
    /// True if the specified association end is the principal in an identifying relationship.
    /// or if the association end has cascade delete defined.
    /// </summary>
    public bool IsCascadeDeletePrincipal(AssociationEndMember associationEnd)
    {
        if (associationEnd == null)
        {
            throw new ArgumentNullException("associationEnd");
        }

        return associationEnd.DeleteBehavior == OperationAction.Cascade || IsPrincipalEndOfIdentifyingRelationship(associationEnd);
    }

    /// <summary>
    /// True if the specified association end is the principal end in an identifying relationship.
    /// In order to be an identifying relationship, the association must have a referential constraint where all of the dependent properties are part of the dependent type's primary key.
    /// </summary>
    public bool IsPrincipalEndOfIdentifyingRelationship(AssociationEndMember associationEnd)
    {
        if (associationEnd == null)
        {
            throw new ArgumentNullException("associationEnd");
        }

        ReferentialConstraint refConstraint = ((AssociationType)associationEnd.DeclaringType).ReferentialConstraints.Where(rc => rc.FromRole == associationEnd).SingleOrDefault();
        if (refConstraint != null)
        {
            EntityType entity = refConstraint.ToRole.GetEntityType();
            return !refConstraint.ToProperties.Where(tp => !entity.KeyMembers.Contains(tp)).Any();
        }
        return false;
    }

    /// <summary>
    /// True if the specified association type is an identifying relationship.
    /// In order to be an identifying relationship, the association must have a referential constraint where all of the dependent properties are part of the dependent type's primary key.
    /// </summary>
    public bool IsIdentifyingRelationship(AssociationType association)
    {
        if (association == null)
        {
            throw new ArgumentNullException("association");
        }

        return IsPrincipalEndOfIdentifyingRelationship(association.AssociationEndMembers[0]) || IsPrincipalEndOfIdentifyingRelationship(association.AssociationEndMembers[1]);
    }

    /// <summary>
    /// requires: firstType is not null
    /// effects: if secondType is among the base types of the firstType, return true,
    /// otherwise returns false.
    /// when firstType is same as the secondType, return false.
    /// </summary>
    public bool IsSubtypeOf(EdmType firstType, EdmType secondType)
    {
        if (secondType == null)
        {
            return false;
        }

        // walk up firstType hierarchy list
        for (EdmType t = firstType.BaseType; t != null; t = t.BaseType)
        {
            if (t == secondType)
                return true;
        }
        return false;
    }

    /// <summary>
    /// Returns the subtype of the EntityType in the current itemCollection
    /// </summary>
    public IEnumerable<EntityType> GetSubtypesOf(EntityType type, ItemCollection itemCollection, bool includeAbstractTypes)
    {
        if (type != null)
        {
            IEnumerable<EntityType> typesInCollection = itemCollection.GetItems<EntityType>();
            foreach (EntityType typeInCollection in typesInCollection)
            {
                if (type.Equals(typeInCollection) == false && this.IsSubtypeOf(typeInCollection, type))
                {
                    if (includeAbstractTypes || !typeInCollection.Abstract)
                    {
                        yield return typeInCollection;
                    }
                }
            }
        }
    }

    public static bool TryGetStringMetadataPropertySetting(MetadataItem item, string propertyName, out string value)
    {
        value = null;
        MetadataProperty property = item.MetadataProperties.FirstOrDefault(p => p.Name == propertyName);
        if (property != null)
        {
            value = (string)property.Value;
        }
        return value != null;
    }
}
/// <summary>
/// Responsible for loading an EdmItemCollection from a .edmx file or .csdl files
/// </summary>
public class MetadataLoader
{
    private readonly DynamicTextTransformation _textTransformation;

    /// <summary>
    /// Initializes an MetadataLoader Instance  with the
    /// TextTransformation (T4 generated class) that is currently running
    /// </summary>
    public MetadataLoader(object textTransformation)
    {
        if (textTransformation == null)
        {
            throw new ArgumentNullException("textTransformation");
        }

        _textTransformation = DynamicTextTransformation.Create(textTransformation);
    }

    /// <summary>
    /// Load the metadata for Edm, Store, and Mapping collections and register them
    /// with a new MetadataWorkspace, returns false if any of the parts can't be
    /// created, some of the ItemCollections may be registered and usable even if false is
    /// returned
    /// </summary>
    public bool TryLoadAllMetadata(string inputFile, out MetadataWorkspace metadataWorkspace)
    {
        metadataWorkspace = new MetadataWorkspace();

        EdmItemCollection edmItemCollection = CreateEdmItemCollection(inputFile);
        metadataWorkspace.RegisterItemCollection(edmItemCollection);


        StoreItemCollection storeItemCollection = null;
        if (TryCreateStoreItemCollection(inputFile, out storeItemCollection))
        {
            StorageMappingItemCollection storageMappingItemCollection = null;
            if (TryCreateStorageMappingItemCollection(inputFile, edmItemCollection, storeItemCollection, out storageMappingItemCollection))
            {
                metadataWorkspace.RegisterItemCollection(storeItemCollection);
                metadataWorkspace.RegisterItemCollection(storageMappingItemCollection);
                return true;
            }
        }

        return false;
    }

    /// <summary>
    /// Create an EdmItemCollection loaded with the metadata provided
    /// </summary>
    public EdmItemCollection CreateEdmItemCollection(string sourcePath, params string[] referenceSchemas)
    {
        EdmItemCollection edmItemCollection;
        if (TryCreateEdmItemCollection(sourcePath, referenceSchemas, out edmItemCollection))
        {
            return edmItemCollection;
        }

        return new EdmItemCollection();
    }

    /// <summary>
    /// Attempts to create a EdmItemCollection from the specified metadata file
    /// </summary>
    public bool TryCreateEdmItemCollection(string sourcePath, out EdmItemCollection edmItemCollection)
    {
        return TryCreateEdmItemCollection(sourcePath, null, out edmItemCollection);
    }

    /// <summary>
    /// Attempts to create a EdmItemCollection from the specified metadata file
    /// </summary>
    public bool TryCreateEdmItemCollection(string sourcePath, string[] referenceSchemas, out EdmItemCollection edmItemCollection)
    {
        edmItemCollection = null;

        if (String.IsNullOrEmpty(sourcePath))
        {
            throw new ArgumentException("sourcePath");
        }

        if (referenceSchemas == null)
        {
            referenceSchemas = new string[0];
        }

        ItemCollection itemCollection = null;
        sourcePath = _textTransformation.Host.ResolvePath(sourcePath);
        EdmItemCollectionBuilder collectionBuilder = new EdmItemCollectionBuilder(_textTransformation, referenceSchemas.Select(s => _textTransformation.Host.ResolvePath(s)).Where(s => s != sourcePath));
        if (collectionBuilder.TryCreateItemCollection(sourcePath, out itemCollection))
        {
            edmItemCollection = (EdmItemCollection)itemCollection;
        }

        return edmItemCollection != null;
    }

    /// <summary>
    /// Attempts to create a StoreItemCollection from the specified metadata file
    /// </summary>
    public bool TryCreateStoreItemCollection(string sourcePath, out StoreItemCollection storeItemCollection)
    {
        storeItemCollection = null;

        if (String.IsNullOrEmpty(sourcePath))
        {
            throw new ArgumentException("sourcePath");
        }

        ItemCollection itemCollection = null;
        StoreItemCollectionBuilder collectionBuilder = new StoreItemCollectionBuilder(_textTransformation);
        if (collectionBuilder.TryCreateItemCollection(_textTransformation.Host.ResolvePath(sourcePath), out itemCollection))
        {
            storeItemCollection = (StoreItemCollection)itemCollection;
        }
        return storeItemCollection != null;
    }

    /// <summary>
    /// Attempts to create a StorageMappingItemCollection from the specified metadata file, EdmItemCollection, and StoreItemCollection
    /// </summary>
    public bool TryCreateStorageMappingItemCollection(string sourcePath, EdmItemCollection edmItemCollection, StoreItemCollection storeItemCollection, out StorageMappingItemCollection storageMappingItemCollection)
    {
        storageMappingItemCollection = null;

        if (String.IsNullOrEmpty(sourcePath))
        {
            throw new ArgumentException("sourcePath");
        }

        if (edmItemCollection == null)
        {
            throw new ArgumentNullException("edmItemCollection");
        }

        if (storeItemCollection == null)
        {
            throw new ArgumentNullException("storeItemCollection");
        }

        ItemCollection itemCollection = null;
        StorageMappingItemCollectionBuilder collectionBuilder = new StorageMappingItemCollectionBuilder(_textTransformation, edmItemCollection, storeItemCollection);
        if (collectionBuilder.TryCreateItemCollection(_textTransformation.Host.ResolvePath(sourcePath), out itemCollection))
        {
            storageMappingItemCollection = (StorageMappingItemCollection)itemCollection;
        }
        return storageMappingItemCollection != null;
    }

    /// <summary>
    /// Gets the Model Namespace from the provided schema file.
    /// </summary>
    public string GetModelNamespace(string sourcePath)
    {
        if (String.IsNullOrEmpty(sourcePath))
        {
            throw new ArgumentException("sourcePath");
        }

        if (sourcePath == "$edmxInputFile$")
        {
            _textTransformation.Errors.Add(new CompilerError(_textTransformation.Host.TemplateFile ?? "Currently Running Template", 0, 0, "",
                "Please overwrite the replacement token '$edmxInputFile$' with the actual name of the .edmx file you would like to generate from."));
            return String.Empty;
        }

        EdmItemCollectionBuilder builder = new EdmItemCollectionBuilder(_textTransformation);
        XElement model;
        if (builder.TryLoadRootElement(_textTransformation.Host.ResolvePath(sourcePath), out model))
        {
            XAttribute attribute = model.Attribute("Namespace");
            if (attribute != null)
            {
                return attribute.Value;
            }
        }

        return String.Empty;
    }

    /// <summary>
    /// base class for ItemCollectionBuilder classes that
    /// load the specific types of metadata
    /// </summary>
    private abstract class ItemCollectionBuilder
    {
        private readonly DynamicTextTransformation _textTransformation;
        private readonly string _fileExtension;
        private readonly string _namespaceV1;
        private readonly string _namespaceV2;
        private readonly string _edmxSectionName;
        private readonly string _rootElementName;

        /// <summary>
        /// FileExtension for individual (non-edmx) metadata file for this
        /// specific ItemCollection type
        /// </summary>
        public string FileExtension
        {
            get { return _fileExtension; }
        }

        /// <summary>
        /// EF Version 1 XmlNamespace name
        /// </summary>
        public string NamespaceV1
        {
            get { return _namespaceV1; }
        }

        /// <summary>
        /// EF Version 2 XmlNamespace name
        /// </summary>
        public string NamespaceV2
        {
            get { return _namespaceV2; }
        }

        /// <summary>
        /// The name of the XmlElement in the .edmx <Runtime> element
        /// to find this ItemCollection's metadata
        /// </summary>
        public string EdmxSectionName
        {
            get { return _edmxSectionName; }
        }

        /// <summary>
        /// The name of the root element of this ItemCollection's metadata
        /// </summary>
        public string RootElementName
        {
            get { return _rootElementName; }
        }

        /// <summary>
        /// Method to build the appropriate ItemCollection
        /// </summary>
        protected abstract ItemCollection CreateItemCollection(IEnumerable<XmlReader> readers, out IList<EdmSchemaError> errors);

        /// <summary>
        /// Ctor to setup the ItemCollectionBuilder members
        /// </summary>
        protected ItemCollectionBuilder(DynamicTextTransformation textTransformation, string fileExtension, string namespaceV1, string namespaceV2, string edmxSectionName, string rootElementName)
        {
            _textTransformation = textTransformation;
            _fileExtension = fileExtension;
            _namespaceV1 = namespaceV1;
            _namespaceV2 = namespaceV2;
            _edmxSectionName = edmxSectionName;
            _rootElementName = rootElementName;
        }

        /// <summary>
        /// Try to create an ItemCollection loaded with the metadata provided
        /// </summary>
        public bool TryCreateItemCollection(string sourcePath, out ItemCollection itemCollection)
        {
            itemCollection = null;

            if (String.IsNullOrEmpty(sourcePath))
            {
                throw new ArgumentException("sourcePath");
            }

            if (sourcePath == "$edmxInputFile$")
            {
                _textTransformation.Errors.Add(new CompilerError(_textTransformation.Host.TemplateFile ?? "Currently Running Template", 0, 0, "",
                    "Please overwrite the replacement token '$edmxInputFile$' with the actual name of the .edmx file you would like to generate from."));
                return false;
            }

            XElement schemaElement = null;
            if (TryLoadRootElement(sourcePath, out schemaElement))
            {
                List<XmlReader> readers = new List<XmlReader>();
                try
                {
                    readers.Add(schemaElement.CreateReader());
                    IList<EdmSchemaError> errors = null;

                    ItemCollection tempItemCollection = CreateItemCollection(readers, out errors);
                    if (ProcessErrors(errors, sourcePath))
                    {
                        return false;
                    }

                    itemCollection = tempItemCollection;
                    return true;
                }
                finally
                {
                    foreach (XmlReader reader in readers)
                    {
                        ((IDisposable)reader).Dispose();
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Tries to load the root element from the metadata file provided
        /// </summary>
        public bool TryLoadRootElement(string sourcePath, out XElement schemaElement)
        {
            schemaElement = null;
            string extension = Path.GetExtension(sourcePath);
            if (extension.Equals(".edmx", StringComparison.InvariantCultureIgnoreCase))
            {
                return TryLoadRootElementFromEdmx(sourcePath, out schemaElement);
            }
            else if (extension.Equals(FileExtension, StringComparison.InvariantCultureIgnoreCase))
            {
                // load from single metadata file (.csdl, .ssdl, or .msl)
                schemaElement = XElement.Load(sourcePath, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Trys to load the root element from the edmxDocument provided
        /// </summary>
        private static bool TryLoadRootElementFromEdmx(XElement edmxDocument, string edmxNamespace, string sectionNamespace, string sectionName, string rootElementName, out XElement rootElement)
        {
            rootElement = null;

            XNamespace edmxNs = edmxNamespace;
            XNamespace sectionNs = sectionNamespace;

            XElement runtime = edmxDocument.Element(edmxNs + "Runtime");
            if (runtime == null)
                return false;

            XElement section = runtime.Element(edmxNs + sectionName);
            if (section == null)
                return false;

            rootElement = section.Element(sectionNs + rootElementName);
            return rootElement != null;
        }

        /// <summary>
        /// Trys to load the root element from the .edmx metadata file provided
        /// </summary>
        private bool TryLoadRootElementFromEdmx(string edmxPath, out XElement rootElement)
        {
            rootElement = null;

            XElement element = XElement.Load(edmxPath, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);

            return TryLoadRootElementFromEdmx(element, MetadataConstants.EDMX_NAMESPACE_V2, NamespaceV2, EdmxSectionName, RootElementName, out rootElement)
                || TryLoadRootElementFromEdmx(element, MetadataConstants.EDMX_NAMESPACE_V1, NamespaceV1, EdmxSectionName, RootElementName, out rootElement);
        }

        /// <summary>
        /// Takes an Enumerable of EdmSchemaErrors, and adds them
        /// to the errors collection of the template class
        /// </summary>
        private bool ProcessErrors(IEnumerable<EdmSchemaError> errors, string sourceFilePath)
        {
            bool foundErrors = false;
            foreach (EdmSchemaError error in errors)
            {
                CompilerError newError = new CompilerError(error.SchemaLocation, error.Line, error.Column,
                                                 error.ErrorCode.ToString(CultureInfo.InvariantCulture),
                                                 error.Message);
                newError.IsWarning = error.Severity == EdmSchemaErrorSeverity.Warning;
                foundErrors |= error.Severity == EdmSchemaErrorSeverity.Error;
                if (error.SchemaLocation == null)
                {
                    newError.FileName = sourceFilePath;
                }
                _textTransformation.Errors.Add(newError);
            }

            return foundErrors;
        }
    }

    /// <summary>
    /// Builder class for creating a StorageMappingItemCollection
    /// </summary>
    private class StorageMappingItemCollectionBuilder : ItemCollectionBuilder
    {
        private readonly EdmItemCollection _edmItemCollection;
        private readonly StoreItemCollection _storeItemCollection;

        public StorageMappingItemCollectionBuilder(DynamicTextTransformation textTransformation, EdmItemCollection edmItemCollection, StoreItemCollection storeItemCollection)
            : base(textTransformation, MetadataConstants.MSL_EXTENSION, MetadataConstants.MSL_NAMESPACE_V1, MetadataConstants.MSL_NAMESPACE_V2, MetadataConstants.MSL_EDMX_SECTION_NAME, MetadataConstants.MSL_ROOT_ELEMENT_NAME)
        {
            _edmItemCollection = edmItemCollection;
            _storeItemCollection = storeItemCollection;
        }

        protected override ItemCollection CreateItemCollection(IEnumerable<XmlReader> readers, out IList<EdmSchemaError> errors)
        {
            return MetadataItemCollectionFactory.CreateStorageMappingItemCollection(_edmItemCollection, _storeItemCollection, readers, out errors);
        }
    }

    /// <summary>
    /// Builder class for creating a StoreItemCollection
    /// </summary>
    private class StoreItemCollectionBuilder : ItemCollectionBuilder
    {
        public StoreItemCollectionBuilder(DynamicTextTransformation textTransformation)
            : base(textTransformation, MetadataConstants.SSDL_EXTENSION, MetadataConstants.SSDL_NAMESPACE_V1, MetadataConstants.SSDL_NAMESPACE_V2, MetadataConstants.SSDL_EDMX_SECTION_NAME, MetadataConstants.SSDL_ROOT_ELEMENT_NAME)
        {
        }

        protected override ItemCollection CreateItemCollection(IEnumerable<XmlReader> readers, out IList<EdmSchemaError> errors)
        {
            return MetadataItemCollectionFactory.CreateStoreItemCollection(readers, out errors);
        }
    }

    /// <summary>
    /// Builder class for creating a EdmItemCollection
    /// </summary>
    private class EdmItemCollectionBuilder : ItemCollectionBuilder
    {
        private List<string> _referenceSchemas = new List<string>();

        public EdmItemCollectionBuilder(DynamicTextTransformation textTransformation)
            : base(textTransformation, MetadataConstants.CSDL_EXTENSION, MetadataConstants.CSDL_NAMESPACE_V1, MetadataConstants.CSDL_NAMESPACE_V2, MetadataConstants.CSDL_EDMX_SECTION_NAME, MetadataConstants.CSDL_ROOT_ELEMENT_NAME)
        {
        }

        public EdmItemCollectionBuilder(DynamicTextTransformation textTransformation, IEnumerable<string> referenceSchemas)
            : this(textTransformation)
        {
            _referenceSchemas.AddRange(referenceSchemas);
        }

        protected override ItemCollection CreateItemCollection(IEnumerable<XmlReader> readers, out IList<EdmSchemaError> errors)
        {
            List<XmlReader> ownedReaders = new List<XmlReader>();
            List<XmlReader> allReaders = new List<XmlReader>();
            try
            {
                allReaders.AddRange(readers);
                foreach (string path in _referenceSchemas.Distinct())
                {
                    XElement reference;
                    if (TryLoadRootElement(path, out reference))
                    {
                        XmlReader reader = reference.CreateReader();
                        allReaders.Add(reader);
                        ownedReaders.Add(reader);
                    }
                }

                return MetadataItemCollectionFactory.CreateEdmItemCollection(allReaders, out errors);
            }
            finally
            {
                foreach (XmlReader reader in ownedReaders)
                {
                    ((IDisposable)reader).Dispose();
                }
            }
        }
    }
}

/// <summary>
/// Responsible for encapsulating the retrieval and translation of the CodeGeneration
/// annotations in the EntityFramework Metadata to a form that is useful in code generation.
/// </summary>
public static class Accessibility
{
    private const string GETTER_ACCESS = "http://schemas.microsoft.com/ado/2006/04/codegeneration:GetterAccess";
    private const string SETTER_ACCESS = "http://schemas.microsoft.com/ado/2006/04/codegeneration:SetterAccess";
    private const string TYPE_ACCESS = "http://schemas.microsoft.com/ado/2006/04/codegeneration:TypeAccess";
    private const string METHOD_ACCESS = "http://schemas.microsoft.com/ado/2006/04/codegeneration:MethodAccess";
    private const string ACCESS_PROTECTED = "Protected";
    private const string ACCESS_INTERNAL = "Internal";
    private const string ACCESS_PRIVATE = "Private";
    private static readonly Dictionary<string, int> AccessibilityRankIdLookup = new Dictionary<string, int>
        {
            { "private", 1},
            { "internal", 2},
            { "protected", 3},
            { "public", 4},
        };

    /// <summary>
    /// Gets the accessibility that should be applied to a type being generated from the provided GlobalItem.
    ///
    /// defaults to public if no annotation is found.
    /// </summary>
    public static string ForType(GlobalItem item)
    {
        if (item == null)
        {
            return null;
        }

        return GetAccessibility(item, TYPE_ACCESS);
    }

    /// <summary>
    /// Gets the accessibility that should be applied at the property level for a property being
    /// generated from the provided EdmMember.
    ///
    /// defaults to public if no annotation is found.
    /// </summary>
    public static string ForProperty(EdmMember member)
    {
        if (member == null)
        {
            return null;
        }

        string getterAccess, setterAccess, propertyAccess;
        CalculatePropertyAccessibility(member, out propertyAccess, out getterAccess, out setterAccess);
        return propertyAccess;
    }

    /// <summary>
    /// Gets the accessibility that should be applied at the property level for a Read-Only property being
    /// generated from the provided EdmMember.
    ///
    /// defaults to public if no annotation is found.
    /// </summary>
    public static string ForReadOnlyProperty(EdmMember member)
    {
        if (member == null)
        {
            return null;
        }

        return GetAccessibility(member, GETTER_ACCESS);
    }

    /// <summary>
    /// Gets the accessibility that should be applied at the property level for a property being
    /// generated from the provided EntitySet.
    ///
    /// defaults to public if no annotation is found.
    /// </summary>
    public static string ForReadOnlyProperty(EntitySet set)
    {
        if (set == null)
        {
            return null;
        }

        return GetAccessibility(set, GETTER_ACCESS);
    }

    /// <summary>
    /// Gets the accessibility that should be applied at the property level for a Write-Only property being
    /// generated from the provided EdmMember.
    ///
    /// defaults to public if no annotation is found.
    /// </summary>
    public static string ForWriteOnlyProperty(EdmMember member)
    {
        if (member == null)
        {
            return null;
        }

        return GetAccessibility(member, SETTER_ACCESS);
    }


    /// <summary>
    /// Gets the accessibility that should be applied at the get level for a property being
    /// generated from the provided EdmMember.
    ///
    /// defaults to empty if no annotation is found or the accessibility is the same as the property level.
    /// </summary>
    public static string ForGetter(EdmMember member)
    {
        if (member == null)
        {
            return null;
        }

        string getterAccess, setterAccess, propertyAccess;
        CalculatePropertyAccessibility(member, out propertyAccess, out getterAccess, out setterAccess);
        return getterAccess;
    }

    /// <summary>
    /// Gets the accessibility that should be applied at the set level for a property being
    /// generated from the provided EdmMember.
    ///
    /// defaults to empty if no annotation is found or the accessibility is the same as the property level.
    /// </summary>
    public static string ForSetter(EdmMember member)
    {
        if (member == null)
        {
            return null;
        }

        string getterAccess, setterAccess, propertyAccess;
        CalculatePropertyAccessibility(member, out propertyAccess, out getterAccess, out setterAccess);
        return setterAccess;
    }

    /// <summary>
    /// Gets the accessibility that should be applied to a method being generated from the provided EdmFunction.
    ///
    /// defaults to public if no annotation is found.
    /// </summary>
    public static string ForMethod(EdmFunction function)
    {
        if (function == null)
        {
            return null;
        }

        return GetAccessibility(function, METHOD_ACCESS);
    }

    private static void CalculatePropertyAccessibility(MetadataItem item,
        out string propertyAccessibility,
        out string getterAccessibility,
        out string setterAccessibility)
    {
        getterAccessibility = GetAccessibility(item, GETTER_ACCESS);
        int getterRank = AccessibilityRankIdLookup[getterAccessibility];

        setterAccessibility = GetAccessibility(item, SETTER_ACCESS);
        int setterRank = AccessibilityRankIdLookup[setterAccessibility];

        int propertyRank = Math.Max(getterRank, setterRank);
        if (setterRank == propertyRank)
        {
            setterAccessibility = String.Empty;
        }

        if (getterRank == propertyRank)
        {
            getterAccessibility = String.Empty;
        }

        propertyAccessibility = AccessibilityRankIdLookup.Where(v => v.Value == propertyRank).Select(v => v.Key).Single();
    }

    private static string GetAccessibility(MetadataItem item, string name)
    {
        string accessibility;
        if (MetadataTools.TryGetStringMetadataPropertySetting(item, name, out accessibility))
        {
            return TranslateUserAccessibilityToCSharpAccessibility(accessibility);
        }

        return "public";
    }

    private static string TranslateUserAccessibilityToCSharpAccessibility(string userAccessibility)
    {
        if (userAccessibility == ACCESS_PROTECTED)
        {
            return "protected";
        }
        else if (userAccessibility == ACCESS_INTERNAL)
        {
            return "internal";
        }
        else if (userAccessibility == ACCESS_PRIVATE)
        {
            return "private";
        }
        else
        {
            // default to public
            return "public";
        }
    }

}

/// <summary>
/// Responsible for creating source code regions in code when the loop inside
/// actually produces something.
/// </summary>
public class CodeRegion
{
    private const int STANDARD_INDENT_LENGTH = 4;

    private readonly DynamicTextTransformation _textTransformation;
    private int _beforeRegionLength;
    private int _emptyRegionLength;
    private int _regionIndentLevel = -1;

    /// <summary>
    /// Initializes an CodeRegion instance with the
    /// TextTransformation (T4 generated class) that is currently running
    /// </summary>
    public CodeRegion(object textTransformation)
    {
        if (textTransformation == null)
        {
            throw new ArgumentNullException("textTransformation");
        }

        _textTransformation = DynamicTextTransformation.Create(textTransformation);
    }

    /// <summary>
    /// Initializes an CodeRegion instance with the
    /// TextTransformation (T4 generated class) that is currently running,
    /// and the indent level to start the first region at.
    /// </summary>
    public CodeRegion(object textTransformation, int firstIndentLevel)
        : this(textTransformation)
    {
        if (firstIndentLevel < 0)
        {
            throw new ArgumentException("firstIndentLevel");
        }

        _regionIndentLevel = firstIndentLevel - 1;
    }

    /// <summary>
    /// Starts the begining of a region
    /// </summary>
    public void Begin(string regionName)
    {
        if (regionName == null)
        {
            throw new ArgumentNullException("regionName");
        }

        Begin(regionName, 1);
    }

    /// <summary>
    /// Start the begining of a region, indented
    /// the numbers of levels specified
    /// </summary>
    public void Begin(string regionName, int levelsToIncreaseIndent)
    {
        if (regionName == null)
        {
            throw new ArgumentNullException("regionName");
        }

        _beforeRegionLength = _textTransformation.GenerationEnvironment.Length;
        _regionIndentLevel += levelsToIncreaseIndent;
        _textTransformation.Write(GetIndent(_regionIndentLevel));
        _textTransformation.WriteLine("#region " + regionName);
        _emptyRegionLength = _textTransformation.GenerationEnvironment.Length;
    }

    /// <summary>
    /// Ends a region, or totaly removes it if nothing
    /// was generted since the begining of the region.
    /// </summary>
    public void End()
    {
        End(1);
    }

    /// <summary>
    /// Ends a region, or totaly removes it if nothing
    /// was generted since the begining of the region, also outdents
    /// the number of levels specified.
    /// </summary>
    public void End(int levelsToDecrease)
    {
        int indentLevel = _regionIndentLevel;
        _regionIndentLevel -= levelsToDecrease;

        if (_emptyRegionLength == _textTransformation.GenerationEnvironment.Length)
            _textTransformation.GenerationEnvironment.Length = _beforeRegionLength;
        else
        {
            _textTransformation.WriteLine(String.Empty);
            _textTransformation.Write(GetIndent(indentLevel));
            _textTransformation.WriteLine("#endregion");
        }
    }

    /// <summary>
    /// Gets the current indent level that the next end region statement will be written
    /// at
    /// </summary>
    public int CurrentIndentLevel { get { return _regionIndentLevel; } }

    /// <summary>
    /// Get a string of spaces equivelent to the number of indents
    /// desired.
    /// </summary>
    public static string GetIndent(int indentLevel)
    {
        if (indentLevel < 0)
        {
            throw new ArgumentException("indentLevel");
        }

        return String.Empty.PadLeft(indentLevel * STANDARD_INDENT_LENGTH);
    }
}




/// <summary>
/// Responsible for collecting together the actual method parameters
/// and the parameters that need to be sent to the Execute method.
/// </summary>
public class FunctionImportParameter
{
    public FunctionParameter Source { get; set; }
    public string RawFunctionParameterName { get; set; }
    public string FunctionParameterName { get; set; }
    public string FunctionParameterType { get; set; }
    public string LocalVariableName { get; set; }
    public string RawClrTypeName { get; set; }
    public string ExecuteParameterName { get; set; }
    public string EsqlParameterName { get; set; }
    public bool NeedsLocalVariable { get; set; }
    public bool IsNullableOfT { get; set; }


    /// <summary>
    /// Creates a set of FunctionImportParameter objects from the parameters passed in.
    /// </summary>
    public static IEnumerable<FunctionImportParameter> Create(IEnumerable<FunctionParameter> parameters, CodeGenerationTools code, MetadataTools ef)
    {
        if (parameters == null)
        {
            throw new ArgumentNullException("parameters");
        }

        if (code == null)
        {
            throw new ArgumentNullException("code");
        }

        if (ef == null)
        {
            throw new ArgumentNullException("ef");
        }

        UniqueIdentifierService unique = new UniqueIdentifierService();
        List<FunctionImportParameter> importParameters = new List<FunctionImportParameter>();
        foreach (FunctionParameter parameter in parameters)
        {
            FunctionImportParameter importParameter = new FunctionImportParameter();
            importParameter.Source = parameter;
            importParameter.RawFunctionParameterName = unique.AdjustIdentifier(code.CamelCase(parameter.Name));
            importParameter.FunctionParameterName = code.Escape(importParameter.RawFunctionParameterName);
            if (parameter.Mode == ParameterMode.In)
            {
                importParameter.NeedsLocalVariable = true;
                importParameter.FunctionParameterType = code.Escape(parameter.TypeUsage);
                importParameter.EsqlParameterName = parameter.Name;
                Type clrType = ef.ClrType(parameter.TypeUsage);
                importParameter.RawClrTypeName = code.Escape(clrType);
                importParameter.IsNullableOfT = clrType.IsValueType;
            }
            else
            {
                importParameter.NeedsLocalVariable = false;
                importParameter.FunctionParameterType = "ObjectParameter";
                importParameter.ExecuteParameterName = importParameter.FunctionParameterName;
            }
            importParameters.Add(importParameter);
        }

        // we save the local parameter uniquification for a second pass to make the visible parameters
        // as pretty and sensible as possible
        for (int i = 0; i < importParameters.Count; i++)
        {
            FunctionImportParameter importParameter = importParameters[i];
            if (importParameter.NeedsLocalVariable)
            {
                importParameter.LocalVariableName = unique.AdjustIdentifier(importParameter.RawFunctionParameterName + "Parameter");
                importParameter.ExecuteParameterName = importParameter.LocalVariableName;
            }
        }

        return importParameters;
    }

    //
    // Class to create unique variables within the same scope
    //
    private sealed class UniqueIdentifierService
    {
        private readonly HashSet<string> _knownIdentifiers;

        public UniqueIdentifierService()
        {
            _knownIdentifiers = new HashSet<string>(StringComparer.Ordinal);
        }

        /// <summary>
        /// Given an identifier, makes it unique within the scope by adding
        /// a suffix (1, 2, 3, ...), and returns the adjusted identifier.
        /// </summary>
        public string AdjustIdentifier(string identifier)
        {
            // find a unique name by adding suffix as necessary
            int numberOfConflicts = 0;
            string adjustedIdentifier = identifier;

            while (!_knownIdentifiers.Add(adjustedIdentifier))
            {
                ++numberOfConflicts;
                adjustedIdentifier = identifier + numberOfConflicts.ToString(CultureInfo.InvariantCulture);
            }

            return adjustedIdentifier;
        }
    }

    private string FunctionImportTypeName(FunctionParameter parameter)
    {
        return parameter.Mode == ParameterMode.In ? parameter.TypeUsage.EdmType.Name : "ObjectParameter";
    }
}

/// <summary>
/// Responsible for marking the various sections of the generation,
/// so they can be split up into separate files
/// </summary>
public class EntityFrameworkTemplateFileManager
{
    /// <summary>
    /// Creates the VsEntityFrameworkTemplateFileManager if VS is detected, otherwise
    /// creates the file system version.
    /// </summary>
    public static EntityFrameworkTemplateFileManager Create(object textTransformation)
    {
        DynamicTextTransformation transformation = DynamicTextTransformation.Create(textTransformation);
        IDynamicHost host = transformation.Host;

#if !PREPROCESSED_TEMPLATE
        if (host.AsIServiceProvider() != null)
        {
            return new VsEntityFrameworkTemplateFileManager(transformation);
        }
#endif
        return new EntityFrameworkTemplateFileManager(transformation);
    }

    private sealed class Block
    {
        public String Name;
        public int Start, Length;
    }

    private readonly List<Block> files = new List<Block>();
    private readonly Block footer = new Block();
    private readonly Block header = new Block();
    private readonly DynamicTextTransformation _textTransformation;

    // reference to the GenerationEnvironment StringBuilder on the
    // TextTransformation object
    private readonly StringBuilder _generationEnvironment;

    private Block currentBlock;

    /// <summary>
    /// Initializes an EntityFrameworkTemplateFileManager Instance  with the
    /// TextTransformation (T4 generated class) that is currently running
    /// </summary>
    private EntityFrameworkTemplateFileManager(object textTransformation)
    {
        if (textTransformation == null)
        {
            throw new ArgumentNullException("textTransformation");
        }

        _textTransformation = DynamicTextTransformation.Create(textTransformation);
        _generationEnvironment = _textTransformation.GenerationEnvironment;
    }

    /// <summary>
    /// Marks the end of the last file if there was one, and starts a new
    /// and marks this point in generation as a new file.
    /// </summary>
    public void StartNewFile(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }

        CurrentBlock = new Block { Name = name };
    }

    public void StartFooter()
    {
        CurrentBlock = footer;
    }

    public void StartHeader()
    {
        CurrentBlock = header;
    }

    public void EndBlock()
    {
        if (CurrentBlock == null)
        {
            return;
        }

        CurrentBlock.Length = _generationEnvironment.Length - CurrentBlock.Start;

        if (CurrentBlock != header && CurrentBlock != footer)
        {
            files.Add(CurrentBlock);
        }

        currentBlock = null;
    }

    /// <summary>
    /// Produce the template output files.
    /// </summary>
    public virtual IEnumerable<string> Process(bool split = true)
    {
        var generatedFileNames = new List<string>();

        if (split)
        {
            EndBlock();

            var headerText = _generationEnvironment.ToString(header.Start, header.Length);
            var footerText = _generationEnvironment.ToString(footer.Start, footer.Length);
            var outputPath = Path.GetDirectoryName(_textTransformation.Host.TemplateFile);

            files.Reverse();

            foreach (var block in files)
            {
                var fileName = Path.Combine(outputPath, block.Name);
                var content = headerText + _generationEnvironment.ToString(block.Start, block.Length) + footerText;

                generatedFileNames.Add(fileName);
                CreateFile(fileName, content);
                _generationEnvironment.Remove(block.Start, block.Length);
            }
        }

        return generatedFileNames;
    }

    protected virtual void CreateFile(string fileName, string content)
    {
        if (IsFileContentDifferent(fileName, content))
        {
            File.WriteAllText(fileName, content);
        }
    }

    protected bool IsFileContentDifferent(String fileName, string newContent)
    {
        return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent);
    }

    private Block CurrentBlock
    {
        get { return currentBlock; }
        set
        {
            if (CurrentBlock != null)
            {
                EndBlock();
            }

            if (value != null)
            {
                value.Start = _generationEnvironment.Length;
            }

            currentBlock = value;
        }
    }

#if !PREPROCESSED_TEMPLATE
    private sealed class VsEntityFrameworkTemplateFileManager : EntityFrameworkTemplateFileManager
    {
        private EnvDTE.ProjectItem templateProjectItem;
        private EnvDTE.DTE dte;
        private Action<string> checkOutAction;
        private Action<IEnumerable<string>> projectSyncAction;

        /// <summary>
        /// Creates an instance of the VsEntityFrameworkTemplateFileManager class with the IDynamicHost instance
        /// </summary>
        public VsEntityFrameworkTemplateFileManager(object textTemplating)
            : base(textTemplating)
        {
            var hostServiceProvider = _textTransformation.Host.AsIServiceProvider();
            if (hostServiceProvider == null)
            {
                throw new ArgumentNullException("Could not obtain hostServiceProvider");
            }

            dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
            if (dte == null)
            {
                throw new ArgumentNullException("Could not obtain DTE from host");
            }

            templateProjectItem = dte.Solution.FindProjectItem(_textTransformation.Host.TemplateFile);

            checkOutAction = fileName => dte.SourceControl.CheckOutItem(fileName);
            projectSyncAction = keepFileNames => ProjectSync(templateProjectItem, keepFileNames);
        }

        public override IEnumerable<string> Process(bool split)
        {
            if (templateProjectItem.ProjectItems == null)
            {
                return new List<string>();
            }

            var generatedFileNames = base.Process(split);

            projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null));

            return generatedFileNames;
        }

        protected override void CreateFile(string fileName, string content)
        {
            if (IsFileContentDifferent(fileName, content))
            {
                CheckoutFileIfRequired(fileName);
                File.WriteAllText(fileName, content);
            }
        }

        private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, IEnumerable<string> keepFileNames)
        {
            var keepFileNameSet = new HashSet<string>(keepFileNames);
            var projectFiles = new Dictionary<string, EnvDTE.ProjectItem>();
            var originalOutput = Path.GetFileNameWithoutExtension(templateProjectItem.FileNames[0]);

            foreach (EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems)
            {
                projectFiles.Add(projectItem.FileNames[0], projectItem);
            }

            // Remove unused items from the project
            foreach (var pair in projectFiles)
            {
                if (!keepFileNames.Contains(pair.Key)
                    && !(Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalOutput + "."))
                {
                    pair.Value.Delete();
                }
            }

            // Add missing files to the project
            foreach (string fileName in keepFileNameSet)
            {
                if (!projectFiles.ContainsKey(fileName))
                {
                    templateProjectItem.ProjectItems.AddFromFile(fileName);
                }
            }
        }

        private void CheckoutFileIfRequired(string fileName)
        {
            if (dte.SourceControl == null
                || !dte.SourceControl.IsItemUnderSCC(fileName)
                    || dte.SourceControl.IsItemCheckedOut(fileName))
            {
                return;
            }

            // run on worker thread to prevent T4 calling back into VS
            checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null));
        }
    }
#endif
}

/// <summary>
/// Responsible creating an instance that can be passed
/// to helper classes that need to access the TextTransformation
/// members.  It accesses member by name and signature rather than
/// by type.  This is necessary when the
/// template is being used in Preprocessed mode
/// and there is no common known type that can be
/// passed instead
/// </summary>
public class DynamicTextTransformation
{
    private object _instance;
    IDynamicHost _dynamicHost;

    private readonly MethodInfo _write;
    private readonly MethodInfo _writeLine;
    private readonly PropertyInfo _generationEnvironment;
    private readonly PropertyInfo _errors;
    private readonly PropertyInfo _host;

    /// <summary>
    /// Creates an instance of the DynamicTextTransformation class around the passed in
    /// TextTransformation shapped instance passed in, or if the passed in instance
    /// already is a DynamicTextTransformation, it casts it and sends it back.
    /// </summary>
    public static DynamicTextTransformation Create(object instance)
    {
        if (instance == null)
        {
            throw new ArgumentNullException("instance");
        }

        DynamicTextTransformation textTransformation = instance as DynamicTextTransformation;
        if (textTransformation != null)
        {
            return textTransformation;
        }

        return new DynamicTextTransformation(instance);
    }

    private DynamicTextTransformation(object instance)
    {
        _instance = instance;
        Type type = _instance.GetType();
        _write = type.GetMethod("Write", new Type[] { typeof(string) });
        _writeLine = type.GetMethod("WriteLine", new Type[] { typeof(string) });
        _generationEnvironment = type.GetProperty("GenerationEnvironment", BindingFlags.Instance | BindingFlags.NonPublic);
        _host = type.GetProperty("Host");
        _errors = type.GetProperty("Errors");
    }

    /// <summary>
    /// Gets the value of the wrapped TextTranformation instance's GenerationEnvironment property
    /// </summary>
    public StringBuilder GenerationEnvironment { get { return (StringBuilder)_generationEnvironment.GetValue(_instance, null); } }

    /// <summary>
    /// Gets the value of the wrapped TextTranformation instance's Errors property
    /// </summary>
    public System.CodeDom.Compiler.CompilerErrorCollection Errors { get { return (System.CodeDom.Compiler.CompilerErrorCollection)_errors.GetValue(_instance, null); } }

    /// <summary>
    /// Calls the wrapped TextTranformation instance's Write method.
    /// </summary>
    public void Write(string text)
    {
        _write.Invoke(_instance, new object[] { text });
    }

    /// <summary>
    /// Calls the wrapped TextTranformation instance's WriteLine method.
    /// </summary>
    public void WriteLine(string text)
    {
        _writeLine.Invoke(_instance, new object[] { text });
    }

    /// <summary>
    /// Gets the value of the wrapped TextTranformation instance's Host property
    /// if available (shows up when hostspecific is set to true in the template directive) and returns
    /// the appropriate implementation of IDynamicHost
    /// </summary>
    public IDynamicHost Host
    {
        get
        {
            if (_dynamicHost == null)
            {
                if (_host == null)
                {
                    _dynamicHost = new NullHost();
                }
                else
                {
                    _dynamicHost = new DynamicHost(_host.GetValue(_instance, null));
                }
            }
            return _dynamicHost;
        }
    }
}


/// <summary>
/// Reponsible for abstracting the use of Host between times
/// when it is available and not
/// </summary>
public interface IDynamicHost
{
    /// <summary>
    /// An abstracted call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolveParameterValue
    /// </summary>
    string ResolveParameterValue(string id, string name, string otherName);

    /// <summary>
    /// An abstracted call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolvePath
    /// </summary>
    string ResolvePath(string path);

    /// <summary>
    /// An abstracted call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost TemplateFile
    /// </summary>
    string TemplateFile { get; }

    /// <summary>
    /// Returns the Host instance cast as an IServiceProvider
    /// </summary>
    IServiceProvider AsIServiceProvider();
}

/// <summary>
/// Reponsible for implementing the IDynamicHost as a dynamic
/// shape wrapper over the Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost interface
/// rather than type dependent wrapper.  We don't use the
/// interface type so that the code can be run in preprocessed mode
/// on a .net framework only installed machine.
/// </summary>
public class DynamicHost : IDynamicHost
{
    private readonly object _instance;
    private readonly MethodInfo _resolveParameterValue;
    private readonly MethodInfo _resolvePath;
    private readonly PropertyInfo _templateFile;

    /// <summary>
    /// Creates an instance of the DynamicHost class around the passed in
    /// Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost shapped instance passed in.
    /// </summary>
    public DynamicHost(object instance)
    {
        _instance = instance;
        Type type = _instance.GetType();
        _resolveParameterValue = type.GetMethod("ResolveParameterValue", new Type[] { typeof(string), typeof(string), typeof(string) });
        _resolvePath = type.GetMethod("ResolvePath", new Type[] { typeof(string) });
        _templateFile = type.GetProperty("TemplateFile");

    }

    /// <summary>
    /// A call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolveParameterValue
    /// </summary>
    public string ResolveParameterValue(string id, string name, string otherName)
    {
        return (string)_resolveParameterValue.Invoke(_instance, new object[] { id, name, otherName });
    }

    /// <summary>
    /// A call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolvePath
    /// </summary>
    public string ResolvePath(string path)
    {
        return (string)_resolvePath.Invoke(_instance, new object[] { path });
    }

    /// <summary>
    /// A call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost TemplateFile
    /// </summary>
    public string TemplateFile
    {
        get
        {
            return (string)_templateFile.GetValue(_instance, null);
        }
    }

    /// <summary>
    /// Returns the Host instance cast as an IServiceProvider
    /// </summary>
    public IServiceProvider AsIServiceProvider()
    {
        return _instance as IServiceProvider;
    }
}

/// <summary>
/// Reponsible for implementing the IDynamicHost when the
/// Host property is not available on the TextTemplating type. The Host
/// property only exists when the hostspecific attribute of the template
/// directive is set to true.
/// </summary>
public class NullHost : IDynamicHost
{
    /// <summary>
    /// An abstraction of the call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolveParameterValue
    /// that simply retuns null.
    /// </summary>
    public string ResolveParameterValue(string id, string name, string otherName)
    {
        return null;
    }

    /// <summary>
    /// An abstraction of the call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolvePath
    /// that simply retuns the path passed in.
    /// </summary>
    public string ResolvePath(string path)
    {
        return path;
    }

    /// <summary>
    /// An abstraction of the call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost TemplateFile
    /// that returns null.
    /// </summary>
    public string TemplateFile
    {
        get
        {
            return null;
        }
    }

    /// <summary>
    /// Returns null.
    /// </summary>
    public IServiceProvider AsIServiceProvider()
    {
        return null;
    }
}

/// <summary>
/// Responsible for encapsulating the constants defined in Metadata
/// </summary>
public static class MetadataConstants
{
    public const string EDMX_NAMESPACE_V1 = "http://schemas.microsoft.com/ado/2007/06/edmx";
    public const string EDMX_NAMESPACE_V2 = "http://schemas.microsoft.com/ado/2008/10/edmx";


    public const string CSDL_EXTENSION = ".csdl";
    public const string CSDL_NAMESPACE_V1 = "http://schemas.microsoft.com/ado/2006/04/edm";
    public const string CSDL_NAMESPACE_V2 = "http://schemas.microsoft.com/ado/2008/09/edm";
    public const string CSDL_EDMX_SECTION_NAME = "ConceptualModels";
    public const string CSDL_ROOT_ELEMENT_NAME = "Schema";
    public const string EDM_ANNOTATION_09_02 = "http://schemas.microsoft.com/ado/2009/02/edm/annotation";

    public const string SSDL_EXTENSION = ".ssdl";
    public const string SSDL_NAMESPACE_V1 = "http://schemas.microsoft.com/ado/2006/04/edm/ssdl";
    public const string SSDL_NAMESPACE_V2 = "http://schemas.microsoft.com/ado/2009/02/edm/ssdl";
    public const string SSDL_EDMX_SECTION_NAME = "StorageModels";
    public const string SSDL_ROOT_ELEMENT_NAME = "Schema";

    public const string MSL_EXTENSION = ".msl";
    public const string MSL_NAMESPACE_V1 = "urn:schemas-microsoft-com:windows:storage:mapping:CS";
    public const string MSL_NAMESPACE_V2 = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
    public const string MSL_EDMX_SECTION_NAME = "Mappings";
    public const string MSL_ROOT_ELEMENT_NAME = "Mapping";
}